package com.itheima.filter;

import cn.hutool.jwt.JWT;
import cn.hutool.jwt.JWTPayload;
import cn.hutool.jwt.JWTUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.annotation.Order;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.stereotype.Component;
import org.springframework.util.MultiValueMap;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

import java.util.ArrayList;
import java.util.List;
import java.util.function.Consumer;

// 1. 使用Order注解，设置执行顺序
// 执行顺序：数字越小，越早执行，数字相同，会根据类名进行字典排序
// TODO 具体数字要根据你项目实际情况，还有过滤器执行顺序来修改
@Order(-1)
@Component
@Slf4j
// 2. 要实现GlobalFilter声明全局过滤器
public class AuthorizeFilter implements GlobalFilter {

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        // 1.获取请求头信息
        // MultiValueMap 这个结构，是Map结构的变种，它允许存在相同名字的多个key
        MultiValueMap<String, String> params = exchange.getRequest().getHeaders();

        // 2.定义一个白名单列表
        // 判断哪些路径要放行（一般来说像登录接口login、发短信接口sendMessage是需要放行的）
        List<String> whiteList = new ArrayList<>();

        // TODO: 这些路径根据你实际的项目来修改！！！
        whiteList.add("/user/login");
        whiteList.add("/user/sendMsg");

        // 2.1. 获取当前请求的地址
        String url = exchange.getRequest().getPath().toString();
        // 2.2. 判断这个地址是不是要放行
        // 拿当前请求地址，和白名单的地址列表进行比对，如果比对相同就证明当前请求要放行，不需要鉴权
        if (whiteList.contains(url)) {
            log.info("我放行了{}", url);
            return chain.filter(exchange);
        }

        // 3.获取头部的token
        // TODO token一般是放在头部的authorization字段上，但是实际开发根据你们的项目标准来改
        String token = params.getFirst("authorization");

        // 3.使用JWT工具类，对token真实性进行校验
        // 参数一：要验证的token
        // 参数二：用来解析token的秘钥
        // TODO 参数二是token秘钥，要根据你实际项目的秘钥来修改
        // TODO: 2022/12/20 这边需要修改，
        boolean verify = JWTUtil.verify(token, "tanhua".getBytes());
        if (!verify) {
            // 阻止（标准：给前端返回401状态码）
            return getVoidMono(exchange);
        }

        // 4. 使用JWT工具类，获取TOKEN中的用户信息
        JWT jwt = JWTUtil.parseToken(token);
        JWTPayload payload = jwt.getPayload();
        if (payload == null) {
            // 阻止
            return getVoidMono(exchange);
        }

        // 5. 获取载荷里面的id字段
        // TODO 获取载荷中的具体字段信息，实际开发的时候，要根据你项目来修改（字段）
        // TODO 这行代码，和服务里面的拦截器代码是对应关系
        Object id = payload.getClaim("id");
        if (id == null) {
            // 阻止
            return getVoidMono(exchange);
        }

        // 6. 把解析到的用户信息，存放到请求头上
        // 因为响应的请求头是只读状态，不能在上面set，所以需要重构一个请求，在重构的请求对象里面设置新的请求头
        ServerHttpRequest requestNew = exchange.getRequest().mutate().headers(new Consumer<HttpHeaders>() {
            @Override
            public void accept(HttpHeaders httpHeaders) {
                // TODO 这一步设置的key和value，要根据你实际项目来修改
                // 参数一：header的key
                // 参数二：header的key所对应的值
                httpHeaders.add("id", id.toString());
            }
        }).build();

        // 7. 把构建好的请求，刷新上去，并且返回给前端
        exchange.mutate().request(requestNew).build();
        log.info("请求头的id:{}", id.toString());
        return chain.filter(exchange);
    }

    /**
     * 返回401状态的通用方法
     *
     * @param exchange exchange
     * @return Mono
     */
    private Mono<Void> getVoidMono(ServerWebExchange exchange) {
        // 1. 获取响应结构，设置状态
        // TODO 返回的状态，要根据你实际项目来修改
        exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
        // 2. 把修改后的返回值结果，响应给前端
        return exchange.getResponse().setComplete();
    }
}